﻿//////////////////////////////////////////////////////////////////////////////
//
//  Copyright © 1998-2024 Glodon Company Limited.  All rights reserved.
//
//  Use of this software is subject to the terms of the Glodon license
//  agreement provided at the time of installation or download, or which
//  otherwise accompanies this software in either electronic or hard copy form.  
//
//////////////////////////////////////////////////////////////////////////////


#pragma once
#include "GnufItem.h"
#include "GnufTableRowHeaderButton.h"
#include "GnufSearchBox.h"

class QMouseEvent;

namespace gnuf {

    class Table;
    class QtTableRow;
    class QtTable;

    class ColorControl;
    class CommonLineStyleButton;
    class WidgetAgent;
    enum HorizontalAlignment
    {
        eAlignment_None = 0,
        eAlignment_Left,
        eAlignment_Center,
        eAlignment_Right
    };
    /*
        Table

        +=======================================================+ ---
        |       |                       |                       |  ^
        |       +-----------------------+-----------------------| Header
        |       |       |       |       |       |       |       |  v
        +=======================================================+ ---
        |       |       |       |       |       |       |       |  ^
        +-------------------------------------------------------+
        |       |       |       |       |       |       |       |
        +-------------------------------------------------------+
        |       |       |       |       |       |       |       |
        +-------------------------------------------------------+ Body
        |       |       |       |       |       |       |       |
        +-------------------------------------------------------+
        |       |       |       |       |       |       |       |
        +-------------------------------------------------------+
        |       |       |       |       |       |       |       |  v
        +=======================================================+ ---
    */

    class GNUF_BASE_EXPORT TableHeaderNode final : public Item
    {
    public:
        using TableHeaderNodes = std::list< gcmp::OwnerPtr<TableHeaderNode> >;
        using RawTableHeaderNodes = std::vector< TableHeaderNode * >;

        static const EItemType Type;
        explicit TableHeaderNode(TableHeaderNode *, gcmp::OwnerPtr< Info > &&);
        explicit TableHeaderNode(Table *, gcmp::OwnerPtr< Info > &&);
        virtual ~TableHeaderNode();

        // Add a sub node under this node.
        TableHeaderNode * addTableHeaderNode(gcmp::OwnerPtr< Info > &&);

        // Return the depth.
        int tableHeaderNodeDepth() const;

        void setPosition(int row, int column, int rowSpanCount = 1, int columnSpanCount = 1);
        struct Position { Position(); int row, column, rowSpanCount, columnSpanCount; };
        const TableHeaderNode::Position & position() const { return m_position; }
        // 自定义该列表头的内容，如添加CheckBox/Button等
        void setCustomContent(gcmp::OwnerPtr<Item> && content) { m_customContent = TransferOwnership(content); }
        const Item * customContent() const { return m_customContent.get(); }
        Item * customContent() { return m_customContent.get(); }

        // 设置该列的对齐方式
        void setHorizontalAlignment(HorizontalAlignment alignment) { m_hAlignment = alignment; }
        HorizontalAlignment horizontalAlignment() const { return m_hAlignment; }

        const TableHeaderNode::TableHeaderNodes & tableHeaderNodes() const { return m_tableHeaderNodes; }
        TableHeaderNode::RawTableHeaderNodes rawTableHeaderNodes();
    protected:
        // Inherits from Item...
        virtual void onDisconnectFromMyQtItem() override {}
        virtual void onPostVisibilityChanged() override;
    private:
        gcmp::OwnerPtr< Info > m_info;
        gcmp::OwnerPtr< Item > m_customContent;
        TableHeaderNode::Position m_position;
        TableHeaderNode::TableHeaderNodes m_tableHeaderNodes;
        HorizontalAlignment m_hAlignment;
    };

    class GNUF_BASE_EXPORT TableRow final
        : public ItemWith<QtTableRow, Item>
    {
        CANNOT_COPY_OR_ASSIGN(TableRow)
    public:
        using Buttons = std::vector< gcmp::OwnerPtr< TableRowHeaderButton > >;
        using Cells = std::list< gcmp::OwnerPtr<Item> >;
        using RawCells = std::vector< Item * >;
        using TableRows = std::list< gcmp::OwnerPtr< TableRow > >;
        using RawTableRows = std::vector< TableRow * >;

        static const EItemType Type;
        explicit TableRow(TableRow *, gcmp::OwnerPtr< Info > &&);
        explicit TableRow(Table *, gcmp::OwnerPtr< Info > &&);
        virtual ~TableRow();

        /// \return Return the table. Return nullptr, otherwise.
        const Table * table() const;
        Table * table();

        /// \brief Add a sub row under this row.
        TableRow * addTableRow(gcmp::OwnerPtr< Info > &&);

        /// \brief Append a sub row,  same as addTableRow
        /// \return if succeed, return the new table row ptr. if not, return invalid ptr
        gcmp::WeakPtr<TableRow> appendTableRow(gcmp::OwnerPtr<Info>&&);

        /// \brief Insert a sub row, default is downward insert
        /// \return if succeed, return the new table row ptr. if not, return invalid ptr
        gcmp::WeakPtr<TableRow> insertTableRow(const gcmp::WeakPtr<const TableRow>&, gcmp::OwnerPtr<Info>&&, bool isDownward = true);

        /// \brief Insert a sub row by Idx
        /// \return if succeed, return the new table row ptr. if not, return invalid ptr
        gcmp::WeakPtr<TableRow> insertTableRow(int idx, gcmp::OwnerPtr<Info>&&);

        /// \brief Get the sub row index
        /// \return if succeed, idx will be assigned the relative index from its parent. if not, idx is a value which is out of range
        bool tableRowIndex(const gcmp::WeakPtr<const TableRow>&, int& idx) const;

        /// \brief Remove the sub row
        bool removeTableRow(gcmp::WeakPtr<TableRow>);

        /// \return Return the maximum button count.
        int buttonCount() const;

        /// \return Return the depth.
        int tableRowDepth() const;

        void setRowHeight(int height);
        int rowHeight() const;

        /// \brief Add buttons on the header.
        void addRowHeaderButton(gcmp::OwnerPtr<Info>&& info, gcmp::OwnerPtr<Info>&& toggleInfo = nullptr);
        /// \return Return all the buttons.
        const TableRow::Buttons & buttons() const { return m_buttons; }
        /// \return Return all the buttons.
        TableRow::Buttons & rawRuttons() { return m_buttons; }
        /// \return Return the index of the button. Return -1, if not found.
        int buttonIndexOf(const TableRowHeaderButton *) const;
        /// \return Return the button at the index. Return nullptr, if the index isn't valid.
        TableRowHeaderButton * buttonAt(const int index);
        /// \return Return the button at the index. Return nullptr, if the index isn't valid.
        const TableRowHeaderButton * buttonAt(const int index) const;

        /// \brief Expand or collapse this table row. By default, expanded.
        void setExpanded(bool);
        /// \brief Return true if expanded. Return false if collapsed.
        bool expanded() const { return m_expanded; }

        /// \return Return true if selectable. Return false if not selectable.
        bool isSelectable() const;
        /// \brief Select or Deselect this table row. By default, unselected.
        void setSelected(bool);
        /// \brief Return true if selected. Return false if unselected.
        bool selected() const { return m_selected; }

        /// \brief Return true if the row is excluded by searching.
        void setExcluded(bool bExcluded) {
            m_excluded = bExcluded;
        };
        bool excluded() const { return m_excluded; }

        /// \brief Add a cell.
        template <class TCell>
        TCell* addCell()
        {
            static_assert(std::is_base_of<Item, TCell>::value,
                "This class must inherit from Item class!");
            gcmp::OwnerPtr<TCell> cell = NEW_AS_OWNER_PTR(TCell, this);
            TCell* ptr = cell.get();
            setCellItem(static_cast<int>(m_cells.size()), TransferOwnership(cell));
            return ptr;
        }

        /// \brief Insert a cell
        void setCellItem(int idx, gcmp::OwnerPtr<Item>&&);
        
        /// \return Return all the cells.
        const TableRow::Cells & cells() const { return m_cells; }
        /// \return Return all the cells.
        TableRow::RawCells rawCells();
        /// \return Return the cell index. Return -1, if not found.
        int cellIndexOf(const Item *) const;
        /// \return Return the cell at the index. Return nullptr, if the index isn't valid.
        Item * cellAt(int index);
        /// \return Return the cell at the index. Return nullptr, if the index isn't valid.
        const Item * cellAt(int index) const;

        /// \brief Begin to rebuild all the cells.
        void beginToRebuildCells();
        /// \brief End to rebuild all the cells.
        void   endToRebuildCells();
        /// \return Return true if rebuilding all the cells. Return false, otherwise.
        bool isRebuildingCells() const { return m_rebuildingCells; }

        /// \return Return all the table rows.
        const TableRow::TableRows & tableRows() const { return m_tableRows; }
        /// \return Return all the table rows.
        TableRow::RawTableRows rawTableRows();

        /// \return Return the row own relative index from its parent. Return -1, if invalid.
        int tableRowIndex() const;

        /// \brief Try to select the table row. Return true if handled. 
        /// Once handled, do not continue to propagate this event.
        bool treatAsSelection(const QMouseEvent *);
        bool treatAsSelection(const QMouseEvent *, TableRow *);
        
        /// \brief Travel the rows, recursively.
        bool travel(const std::function<bool(TableRow *)> & callback);

        void setInternalNameForJournal(const std::wstring& name);
        std::wstring internalNameForJournal();

    protected:
        // Inherits from Item...
        virtual void onDisconnectFromMyQtItem() override;
        virtual void onPostVisibilityChanged() override;
        virtual void onChildHeightChanged(Item* item, int height);
        virtual bool clickOnTable(QMouseEvent* pEvt) override
        {
            return treatAsSelection(pEvt);
        }

    private:
        void onExpansionChanged(bool recusively);
        int realIndex() const;
        int allsubRowCount() const;

    private:
        gcmp::OwnerPtr< Info > m_info;
        bool m_expanded;
        bool m_selected;
        bool m_excluded = false;
        TableRow::Buttons m_buttons;
        TableRow::TableRows m_tableRows;
        TableRow::Cells m_cells;
        bool m_rebuildingCells;

        std::wstring m_internalNameForJournal;
        friend class Table;
    };
    
    class GNUF_BASE_EXPORT Table final
        : public ItemWith<QtTable, RootItem>
        , public SearchDataModel
    {
        CANNOT_COPY_OR_ASSIGN(Table)
    public:
        using TableRows = std::list< gcmp::WeakPtr< TableRow > >;
        using RawTableRows = std::vector< TableRow * >;
        using TableColumn = std::vector< Item * >;
        using FilterFunc = std::function<bool(const TableRow*, const std::wstring&)>;

        static const EItemType Type;
        explicit Table();
        virtual ~Table();

        virtual const std::list<std::wstring> search(const std::wstring&) override;

        /// \brief Add a header node.
        TableHeaderNode * addTableHeaderNode(gcmp::OwnerPtr< Info > &&);

        /// \brief Add a row.
        TableRow * addTableRow(gcmp::OwnerPtr< Info > &&);

        /// \brief Append a row,  same as addTableRow
        /// \return if succeed, return the new table row ptr. if not, return invalid ptr
        gcmp::WeakPtr<TableRow> appendTableRow(gcmp::OwnerPtr<Info>&&);

        /// \brief Insert a row, default is downward insert
        /// \return if succeed, return the new table row ptr. if not, return invalid ptr
        gcmp::WeakPtr<TableRow> insertTableRow(const gcmp::WeakPtr<const TableRow>&, gcmp::OwnerPtr<Info>&&, bool isDownward = true);

        /// \brief Insert a row by Idx
        /// \return if succeed, return the new table row ptr. if not, return invalid ptr
        gcmp::WeakPtr<TableRow> insertTableRow(int idx, gcmp::OwnerPtr<Info>&&);

        /// \brief Get the row index
        /// \return if succeed, idx will be assigned the relative index from its parent. if not, idx is a value which is out of range
        bool tableRowIndex(const gcmp::WeakPtr<const TableRow>&, int& idx) const;

        /// \brief Remove the row
        bool removeTableRow(gcmp::WeakPtr<TableRow>);

        /// \return Return the column count.
        int columnCount() const;

        /// \return Return all the table header nodes.
        const TableHeaderNode::TableHeaderNodes & tableHeaderNodes() const { return m_tableHeaderNodes; }
        /// \return Return all the table header nodes.
        TableHeaderNode::RawTableHeaderNodes rawTableHeaderNodes();
        
        /// \return Return the table header node at the index.
        const TableHeaderNode * headerNodeAt(int columnIndex) const;

        /// \brief Return all the table rows.
        const TableRow::TableRows & tableRows() const { return m_tableRows; }
        /// \brief Return all the table rows.
        TableRow::RawTableRows rawTableRows();

        /// \brief Set/Get the width for each column.
        void setColumnWidth(const std::vector<int> & columnWidth);
        const std::vector<int> & columnWidth() const { return m_columnWidth; }

        /// \brief Set/Get the height for all rows.
        void setRowHeight(int);
        int rowHeight() const { return m_rowHeight; }

        /// \brief Set/Get if editable.
        void setIsEditable(bool isEditable) { m_isEditable = isEditable; }
        bool isEditable() const { return m_isEditable; }

        /// \brief Set/Get the button count on the row table header.
        void setButtonCountOnRowTableHeader(int value) {
            m_buttonCountOnRowTableHeader = value;
        }
        int buttonCountOnRowTableHeader() const { 
            return m_buttonCountOnRowTableHeader;
        }

        /// \brief Set the visibility of the column.
        void setVisible(const TableHeaderNode *, bool visible);

        void onExpansionChanged(const TableRow *);

        /// \brief Try to select the mouse event on the table row. Return true if
        /// handled. Once handled, do not continue to propagate this event.
        bool treatAsSelection(const QMouseEvent *, TableRow *);
        
        /// \return Return the selected table rows.
        const Table::TableRows & selectedTableRows() const
        {
            wringSelectedTableRows();
            return m_selectedTableRows; 
        }
        /// \return Return the selected table rows.
        Table::RawTableRows selectedRawTableRows();
        /// \return Return the selected table column.
        /// \param columnIndex: The column index.
        Table::TableColumn selectedTableColumn(int columnIndex);
        /// \return Return the column index. Return -1 if failed.
        int columnIndexOf(const Item * pCell) const;
        
        /// \brief Travel the rows, recursively.
        void travel(const std::function<bool(TableRow *)> & callback);

        /// \brief Set true to want to select those table rows. By default, it is false.
        bool wantToSelectTableRow() const { return m_wantToSelectTableRow; }
        /// \return Return true if want to select those table rows.
        void setWantToSelectTableRow(bool val) { m_wantToSelectTableRow = val; }

        struct SearchOption
        {
            /// Want to care about the parent table rows that
            /// have child table rows.
            bool wantToCareAboutParentTableRows = false;

            /// Want to hide the childless table rows that
            /// do NOT have any child table row in the search result.
            bool wantToHideChildlessTableRows = true;

            /// Want to care only about the selected table rows.
            bool wantToCareOnlyAboutTheSeclectedTableRows = false;

            /// Provide an opportunity to the client that is using the table, to
            /// determine if the table row is excluded from the search result, or not.
            /// If return true, the table row will be excluded from the search result, and hidden.
            /// If return false, the table row will be included in the search result, and shown.
            FilterFunc filter;
        };

        SearchOption& searchOption() {
            return m_searchOption;
        };
        
        void onCellsRebuiltUnder(const TableRow *);

        /// \brief set allow add sub row item, allowed by default
        void setAllowToAddSubTableRow(bool bAllow) { m_bAllowAddSubRow = bAllow; };
        bool allowToAddSubTableRow() { return m_bAllowAddSubRow; }

        void setUseInternalRowNameForJournal(bool);
        bool useInternalRowNameForJournal() { return m_bUseInternalRowNameForJournal; };

    protected:
        // Inherits from Item...
        virtual void onDisconnectFromMyQtItem() override;

    private:
        enum class ESelectionMethod : std::uint8_t
        {
            eUnknown,
            eSingle,            // single selection
            eMultipleOneByOne,  // multiple selection one by one
            eMultipleByRange    // multiple selection by range
        };
        bool selectRange(const TableRow *);
        void clearSelectedTableRows();
        void wringSelectedTableRows() const;
        void recollectSelectedTableRows();
        Table::ESelectionMethod m_lastSelectionMethod;
        mutable Table::TableRows m_selectedTableRows;

        TableHeaderNode::TableHeaderNodes m_tableHeaderNodes;
        TableRow::TableRows m_tableRows;
        std::vector<int> m_columnWidth;
        int m_rowHeight;
        int m_buttonCountOnRowTableHeader;
        bool m_isEditable;
        bool m_wantToSelectTableRow;
        SearchOption m_searchOption;
        bool m_bAllowAddSubRow = true;
        bool m_bUseInternalRowNameForJournal = false;
    };

}
